﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using SharpCmd.IO;

namespace SharpCmd.Modules
{
    /// <summary>
    /// Manages icons
    /// </summary>
    internal class IconManager
    {
        static IconManager()
        {
            Instance = new IconManager();
            Icons = new Dictionary<string, IconInfo>();
        }

        internal static IconManager Instance { get; private set; }

        #region interop
        [DllImport("shell32.dll")]
        private static extern IntPtr SHGetFileInfo(
            string pszPath,
            uint dwFileAttributes,
            ref SHFILEINFO psfi,
            uint cbFileInfo,
            uint uFlags);

        [StructLayout(LayoutKind.Sequential)]
        private struct SHFILEINFO
        {
            private const int DISPLAY_NAME_MAX = 256;
            public const int NAMESIZE = 80;
            public IntPtr hIcon;
            public int iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = DISPLAY_NAME_MAX)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAMESIZE)]
            public string szTypeName;
        }

        [DllImport("User32.dll")]
        private static extern int DestroyIcon(IntPtr hIcon);

        private const uint SHGFI_ICON = 0x000000100;
        private const uint SHGFI_DISPLAYNAME = 0x000000200;
        private const uint SHGFI_TYPENAME = 0x000000400;
        private const uint SHGFI_ATTRIBUTES = 0x000000800;
        private const uint SHGFI_ICONLOCATION = 0x000001000;
        private const uint SHGFI_EXETYPE = 0x000002000;
        private const uint SHGFI_SYSICONINDEX = 0x000004000;
        private const uint SHGFI_LINKOVERLAY = 0x000008000;
        private const uint SHGFI_SELECTED = 0x000010000;
        private const uint SHGFI_ATTR_SPECIFIED = 0x000020000;
        private const uint SHGFI_LARGEICON = 0x000000000;
        private const uint SHGFI_SMALLICON = 0x000000001;
        private const uint SHGFI_OPENICON = 0x000000002;
        private const uint SHGFI_SHELLICONSIZE = 0x000000004;
        private const uint SHGFI_PIDL = 0x000000008;
        private const uint SHGFI_USEFILEATTRIBUTES = 0x000000010;
        private const uint SHGFI_ADDOVERLAYS = 0x000000020;
        private const uint SHGFI_OVERLAYINDEX = 0x000000040;

        private const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
        private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
        #endregion

        public enum IconSize
        {
            /// <summary>
            /// Large icon 32x32
            /// </summary>
            Large = 0,

            /// <summary>
            /// Small icon 16x16
            /// </summary>
            Small = 1
        }

        private static Icon GetIcon(string path, IconSize size, bool overlay, FSObjectType type)
        {
            //TODO use GetCachedIcon
            SHFILEINFO info = new SHFILEINFO();
            uint flags = SHGFI_ICON | SHGFI_USEFILEATTRIBUTES;
            if (overlay)
                flags += SHGFI_ADDOVERLAYS;
            flags += size == IconSize.Small ? SHGFI_SMALLICON : SHGFI_LARGEICON;
            uint attributes = type == FSObjectType.File ? FILE_ATTRIBUTE_NORMAL : FILE_ATTRIBUTE_DIRECTORY;
            SHGetFileInfo(
                path,
                attributes,
                ref info,
                (uint)Marshal.SizeOf(info),
                flags);
            Icon icon = null;
            if (info.hIcon != IntPtr.Zero)
                icon = (Icon)Icon.FromHandle(info.hIcon).Clone();
            DestroyIcon(info.hIcon);
            return icon;
        }

        /// <summary>
        /// Get Icon for a file
        /// </summary>
        /// <param name="name">full name or extension (!) INCLUDING dot (.txt)</param>
        /// <returns></returns>
        public static Icon GetFileIcon(string filename, IconSize size, bool overlay)
        {
            //return GetIcon(filename, size, overlay, FSObjectType.File);
            return GetCachedIcon(filename, false);
        }

        /// <summary>
        /// Get icon for a directory
        /// </summary>
        /// <returns></returns>
        public static Icon GetDirectoryIcon(string filename, IconSize size, bool overlay)
        {
            //return GetIcon(filename, size, overlay, FSObjectType.Directory);
            return GetCachedIcon(filename, true);
        }

        private static Icon GetCachedIcon(string path, bool directory)
        {
/*#if DEBUG
            System.Diagnostics.Debug.WriteLine("icons:" + Icons.Count + " mem:" + (GC.GetTotalMemory(false) / 1024) + "k");
#endif*/
            if (Icons.Count > MAX_CACHE_COUNT)
                CleanupCache();
            if (!Icons.ContainsKey(path))
                Icons[path] = new IconInfo()
                {
                    Icon = directory ? GetIcon(path, IconSize.Small, true, FSObjectType.Directory) : GetIcon(path, IconSize.Small, true, FSObjectType.File),
                    CreateTime = Environment.TickCount
                };
            return Icons[path].Icon;
        }

        private static void CleanupCache()
        {
            //delete half of old icons
            var iconsToDelete = Icons.OrderBy(p => p.Value.CreateTime).Take(Icons.Count / 2);
            foreach (var iconToDelete in iconsToDelete)
            {
                Icons.Remove(iconToDelete.Key);
                iconToDelete.Value.Icon.Dispose();
            }
        }

        private static int MAX_CACHE_COUNT = 6000; // we can store 6000 icons in the cache, half will be deleted on cleanup

        private static Dictionary<string, IconInfo> Icons;

        class IconInfo
        {
            public Icon Icon;
            public long CreateTime; // TickCount
        }
    }
}